package me.ahoukuse.koala.controller;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import me.ahoukuse.koala.config.KoalaConfigure;
import me.ahoukuse.koala.dao.FileEntityDao;
import me.ahoukuse.koala.po.DownloadInfo;
import me.ahoukuse.koala.po.FileEntity;
import me.ahoukuse.koala.po.FolderNode;
import me.ahoukuse.koala.po.HttpHeaderRange;
import me.ahoukuse.koala.service.FileService;
import me.ahoukuse.koala.service.SharedService;
import me.ahoukuse.koala.util.FileUtil;
import me.ahoukuse.koala.util.JwtUtil;
import me.ahoukuse.koala.util.MessageUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipOutputStream;

@RestController
@RequestMapping("/file")
public class FileController {

    @Autowired
    FileEntityDao fileEntityDao;

    @Autowired
    FileService fileService;

    @Autowired
    KoalaConfigure koalaConfigure;

    @Autowired
    SharedService sharedService;

    @Autowired
    RedisTemplate<String, Object> redisTemplate;

    private final String split = File.separator;


    @RequestMapping("upload")
    public JSONObject upload(@RequestBody MultipartFile file, @RequestParam(required = false, defaultValue = "0") int parentId) {
        JSONObject res = FileUtil.persistMultipartFile(file, koalaConfigure.getUploadedFileLocation());
        if (res.getBoolean("success")) {
            int userId = JwtUtil.getUserIdFromJwt();
            String filePath = res.getString("fileName");
            FileEntity entity = new FileEntity();
            entity.setFileName(file.getOriginalFilename());
            entity.setFilePath(filePath);
            entity.setType(0);
            entity.setFileType(FileUtil.getFileType(file.getOriginalFilename()));
            entity.setOwnerId(userId);
            entity.setParentId(parentId);
            entity.setFileSize(file.getSize()); // getSize返回的单位是字节
            entity.setCreateTime(new Date());

            fileEntityDao.insertOne(entity);

            return MessageUtil.msg(200, "success");
        } else {
            return MessageUtil.msg(500, "error");
        }
    }

    @PostMapping("initChunkUpload")
    public JSONObject initChunkUpload(String fileName, String fileHash, int numChunk, @RequestParam(required = false, defaultValue = "0") int parentId) {
        return fileService.initChunkUpload(fileName, fileHash, numChunk, parentId);
    }

    @PostMapping("checkChunk")
    public JSONObject checkChunk(String chunkId, String fileName, int chunkIdx, int chunkSize) {
        return fileService.checkChunk(chunkId, fileName, chunkIdx, chunkSize);
    }

    @PostMapping("chunkUpload")
    public JSONObject chunkUpload(String chunkId, String fileName, int chunkIdx, int chunkSize,@RequestParam("chunk") MultipartFile chunk, @RequestParam(required = false, defaultValue = "0") int parentId) {
        return fileService.chunkUpload(chunkId, fileName, chunkIdx, chunkSize, chunk, parentId);
    }

    @PostMapping("finishChunkUpload")
    public JSONObject finishChunkUpload(String chunkId, String fileName, @RequestParam(required = false, defaultValue = "0") int parentId) {
        return fileService.finishChunkUpload(chunkId, fileName, parentId);
    }

    @PostMapping("rename")
    public JSONObject rename(Integer fileId, String newName) {
        // TODO 要检验权限
        fileEntityDao.updateFileName(fileId, newName);
        return MessageUtil.msg(200, "Ok");
    }

    @RequestMapping("getDownLoadToken")
    public JSONObject getDownLoadToken(String fileIds) {
        List<String> ids = Arrays.asList(fileIds.split(","));
        List<FileEntity> fileEntities = fileEntityDao.getFileByIds(ids);
        String token = UUID.randomUUID().toString().replaceAll("-", "");
        if (fileEntities.size() == 1) {
            DownloadInfo info = new DownloadInfo();
            info.setFileName(fileEntities.get(0).getFileName());
            info.setFilePath(koalaConfigure.getUploadedFileLocation() + split + fileEntities.get(0).getFilePath());
            redisTemplate.opsForValue().set("downloadInfo:"+token, info, 2, TimeUnit.HOURS);
        } else {
            String filePath = koalaConfigure.getTemp() + split + token + ".zip";
            DownloadInfo info = new DownloadInfo();
            info.setFileName("批量下载.zip");
            info.setFilePath(filePath);
            redisTemplate.opsForValue().set("downloadInfo:"+token, info, 2, TimeUnit.HOURS);
            FileInputStream fis = null;
            try (FileOutputStream fos = new FileOutputStream(filePath);
                 ZipOutputStream zipOut = new ZipOutputStream(fos)) {
                for (FileEntity fileEntity : fileEntities) {
                    File file = new File(koalaConfigure.getUploadedFileLocation() + split + fileEntity.getFilePath());
                    fis = new FileInputStream(file);
                    ZipEntry zipEntry = new ZipEntry(fileEntity.getFileName());
                    zipOut.putNextEntry(zipEntry);
                    byte[] bytes = new byte[2048];
                    int length;
                    while ((length = fis.read(bytes)) >= 0) {
                        zipOut.write(bytes, 0, length);
                    }

                    fis.close();
                    zipOut.closeEntry();
                }

            } catch (ZipException e) {

            } catch (IOException e) {
                throw new RuntimeException("Error creating zip file", e);
            } finally {
                if (fis != null) {
                    try {
                        fis.close();
                    } catch (IOException ignored) {

                    }
                }
            }

        }
        JSONObject res = MessageUtil.msg(200, "Ok");
        JSONObject info = new JSONObject();
        info.put("token", token);
        res.put("data", info);
        return res;
    }

    @RequestMapping("downloadByToken")
    public void  downloadByToken(HttpServletRequest request, HttpServletResponse response, String token) {
        DownloadInfo downloadInfo = (DownloadInfo) redisTemplate.opsForValue().get("downloadInfo:"+token);
        if (downloadInfo == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return ;
        }
        long fileSize = 0;
        try {
            fileSize = FileUtil.getFileSize(downloadInfo.getFilePath());
        } catch (IOException e) {
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            return ;
        }
        String rangeHeader = request.getHeader(HttpHeaders.RANGE);
        HttpHeaderRange range = new HttpHeaderRange(0, fileSize);
        HttpStatus status;
        if (rangeHeader == null || rangeHeader.equals("")) {
            status = HttpStatus.OK;
        } else {
            range = parseRange(rangeHeader);
            status  = HttpStatus.PARTIAL_CONTENT;
        }
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
        response.setContentLength((int) fileSize);
        response.setHeader("Accept-Ranges", "bytes");
        String encodedFileName = URLEncoder.encode(downloadInfo.getFileName(), StandardCharsets.UTF_8);
        response.setHeader("Content-Disposition", "attachment; filename=" + encodedFileName);
        response.setStatus(status.value());
        if (status.equals(HttpStatus.PARTIAL_CONTENT)) {
            response.setHeader("Content-Range", "bytes " + range.getStart() + "-" + range.getEnd() + "/" + fileSize);
            byte[] fileContentBytes;
            try (RandomAccessFile raf = new RandomAccessFile(downloadInfo.getFilePath(), "r")) {
                raf.seek(range.getStart());
                long contentLength = range.getEnd() - range.getStart() + 1;
                fileContentBytes = new byte[(int) contentLength];
                raf.read(fileContentBytes, 0, (int) contentLength);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        } else {
            try (InputStream inputStream = Files.newInputStream(Path.of(downloadInfo.getFilePath()));
                 var outputStream = response.getOutputStream()) {
                byte[] buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
            } catch (IOException e) {

            }
        }
    }

    private HttpHeaderRange parseRange(String httpRangeList) {
        // 实现解析逻辑，这里简化为只处理单一范围
        // 格式如 "bytes=start-end"
        String[] range = httpRangeList.split("=")[1].split("-");
        long start = Long.parseLong(range[0]);
        long end = Long.parseLong(range[1]);
        return new HttpHeaderRange(start, end);
    }

    private void readFileData(BufferedInputStream bis, OutputStream outputStream, HttpHeaderRange range) {
        byte[] buffer = new byte[8192];
        int bytesRead;
        long bytesToRead = range.getEnd() - range.getStart() + 1;
        while (true) {
            try {
                if (!(bytesToRead > 0 && (bytesRead = bis.read(buffer)) != -1)) break;
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            long bytesToWrite = Math.min(bytesRead, bytesToRead);
            try {
                outputStream.write(buffer,0, (int) bytesToWrite);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            bytesToRead -= bytesToWrite;
        }
    }


    @RequestMapping("getDownLoadTokenShared")
    public JSONObject getDownLoadTokenShared(String fileIds) {
        return MessageUtil.msg(200, "Ok");
    }

    @RequestMapping("download")
    public ResponseEntity<Resource> batchDownload(String fileIds) {
        // 解析文件路径

        List<String> ids = Arrays.asList(fileIds.split(","));

        List<FileEntity> fileEntities = fileEntityDao.getFileByIds(ids);

        if (fileEntities.size() == 1) {
            File filePath = new File(koalaConfigure.getUploadedFileLocation() + split + fileEntities.get(0).getFilePath());

            // 创建Resource对象，它用于包装文件，并提供一些有用的方法
            InputStreamResource resource = null;
            try {
                resource = new InputStreamResource(new FileInputStream(filePath));
            } catch (FileNotFoundException e) {
                throw new RuntimeException(e);
            }

            // 设置响应头
            HttpHeaders headers = new HttpHeaders();
            String encodedFileName = URLEncoder.encode(fileEntities.get(0).getFileName(), StandardCharsets.UTF_8);
            headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + encodedFileName );

            // 设置文件类型
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);

            // 返回ResponseEntity
            return ResponseEntity.ok()
                    .headers(headers)
                    .body(resource);
        } else if (fileEntities.size() > 1) {
            // 如果有多个文件，打包为zip压缩包下载
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            FileInputStream fis = null;
            try (ZipOutputStream zipOut = new ZipOutputStream(baos)) {
                for (FileEntity fileEntity : fileEntities) {
                    File file = new File(koalaConfigure.getUploadedFileLocation() + split + fileEntity.getFilePath());
                    fis = new FileInputStream(file);
                    ZipEntry zipEntry = new ZipEntry(fileEntity.getFileName());
                    zipOut.putNextEntry(zipEntry);
                    byte[] bytes = new byte[2048];
                    int length;
                    while ((length = fis.read(bytes)) >= 0) {
                        zipOut.write(bytes, 0, length);
                    }

                    fis.close();
                    zipOut.closeEntry();
                }

            } catch (ZipException e) {

            } catch (IOException e) {
                throw new RuntimeException("Error creating zip file", e);
            } finally {
                if (fis != null) {
                    try {
                        fis.close();
                    } catch (IOException ignored) {

                    }
                }
            }

            // 返回zip文件
            byte[] zipBytes = baos.toByteArray();
            InputStreamResource resource = new InputStreamResource(new ByteArrayInputStream(zipBytes));

            HttpHeaders headers = new HttpHeaders();
            headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=downloadedFiles.zip");
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);

            return ResponseEntity.ok().headers(headers).body(resource);
        } else {
            // 如果没有文件
            return ResponseEntity.notFound().build();
        }
    }

    @PostMapping("delete")
    public JSONObject deleteFiles(String ids) {
        fileEntityDao.deleteByIds(Arrays.asList(ids.split(",")));
        JSONObject res = MessageUtil.msg(200, "success");
        return res;
    }

    @GetMapping("getFolderByFid")
    public JSONObject getFolderByFid(int parentId) {
        int userId = JwtUtil.getUserIdFromJwt();
        List<FolderNode> folders = fileEntityDao.getFolderByFid(userId, parentId);
        JSONArray jsonArray = new JSONArray();
        folders.forEach((folder) -> {
            JSONObject item = new JSONObject();
            item.put("id", folder.getId());
            item.put("name", folder.getFileName());
            item.put("hasChildren", folder.getSubFolderCount() != 0);
            jsonArray.add(item);
        });
        JSONObject res = MessageUtil.msg(200, "success");
        JSONObject data = new JSONObject();
        data.put("folders", jsonArray);
        res.put("data", data);
        return res;
    }


    @PostMapping("copyTo")
    public JSONObject copyFilesTO(String ids, int destFolderId) {
        JSONObject res = fileService.copyFilesTo(Arrays.asList(ids.split(",")), destFolderId);
        return res;
    }

    @PostMapping("moveTo")
    public JSONObject moveFilesTO(String ids, int destFolderId) {
        JSONObject res = fileService.moveFilesTo(Arrays.asList(ids.split(",")), destFolderId);
        return res;
    }

    @PostMapping("share")
    public JSONObject share(String ids) {
        int userId = JwtUtil.getUserIdFromJwt();
        List<String> fileIds = Arrays.asList(ids.split(","));
        return sharedService.newShare(userId, fileIds);
    }

    @GetMapping("getSharedFiles")
    public JSONObject getSharedFiles(String sharedCode) {
        List<FileEntity> sharedFiles = fileEntityDao.getFileBySharedCode(sharedCode);
        JSONObject res = MessageUtil.msg(200, "Ok");
        JSONObject data = new JSONObject();
        data.put("files", sharedFiles);
        res.put("data", data);
        return res;
    }

}